﻿using System;
using System.Linq;
using System.Reflection.Emit;
using msilgc.cbuilder.CSyntaxTree;
using msilgc.cbuilder.MSILSyntaxTree;
using msilgc.CSyntaxTree;

namespace msilgc.cbuilder.CBuilder {
    internal class LoadFieldTranslator : ITranslator {
        private readonly VariableFactory _varFactory;

        public LoadFieldTranslator(VariableFactory varFactory)
        {
            _varFactory = varFactory;
        }

        private static int ToInt16(byte[] data) {
            return (data[0] << 0x0) | (data[1] << 0x8);
        }

        private static int ToInt8(byte[] data) {
            return (data[0] << 0x0);
        }

        private static int GetArgIndex(Instruction instruction)
        {
            int argIndex;
            if (instruction.Inst == OpCodes.Ldarga) {
                argIndex = ToInt16(instruction.Data);
            } else if (instruction.Inst == OpCodes.Ldarga_S) {
                argIndex = ToInt8(instruction.Data);
            } else {
                throw new Exception("unexpected instruction: " + instruction);
            }
            return argIndex;
        }

        private static Instruction GetLastInstruction(BasicBlock block, Instruction instruction)
        {
            return block.Instructions[block.Instructions.IndexOf(instruction) - 1];
        }

        private static bool IsGrainFieldLoadInstruction(ICMethodBody methodBody, BasicBlock block, Instruction instruction)
        {
            if (block.Instructions.IndexOf(instruction) == 0)
            {
                return false;
            }
            Instruction lastInstruction = GetLastInstruction(block, instruction);
            return (lastInstruction.Inst == OpCodes.Ldarga || lastInstruction.Inst == OpCodes.Ldarga_S) &&
                methodBody.Arguments.ElementAt(GetArgIndex(lastInstruction)) == null;
        }

        private static string GetGrainFieldName(Instruction instruction)
        {
            return instruction.FieldOperand.Name.ToLower();
        }

        public ICStatement TranslateAndAdd(CMethodBody methodBody, CBasicBlock currentBlock, BasicBlock block, Instruction instruction)
        {
            if(IsGrainFieldLoadInstruction(methodBody, block, instruction))
            {
                int index = GetArgIndex(GetLastInstruction(block, instruction));
                if(index == methodBody.BlockIdxVariableOffset) {
                    return CreateAndAddGrainAssignStm(currentBlock, "blockIdx", GetGrainFieldName(instruction));
                } else if(index == methodBody.ThreadIdxVariableOffset) {
                    return CreateAndAddGrainAssignStm(currentBlock, "threadIdx", GetGrainFieldName(instruction));
                } else if(index == methodBody.ThreadDimVariableOffset) {
                    throw new Exception("Cannot refer to threadDim in code");
                } else if(index == methodBody.BlockDimVariableOffset) {
                    return CreateAndAddGrainAssignStm(currentBlock, "blockDim", GetGrainFieldName(instruction));
                } else {
                    throw new Exception("unexpected state");
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        private ICStatement CreateAndAddGrainAssignStm(CBasicBlock currentBlock, string variableName, string fieldName)
        {
            IVariable grainTargetVar = _varFactory.CreateNext(typeof(int));
            IVariable grainSourceVariable = new Variable(variableName + "." + fieldName, null);
            AssignToLocalVariable assignStm = new AssignToLocalVariable(grainSourceVariable, grainTargetVar);
            currentBlock.Statements.Add(assignStm);
            return assignStm;
        }
    }
}
